agora inbox for [email protected]
help / color / mirror / Atom feedPossible future performance improvement: sort updates/deletes by ctid
268+ messages / 4 participants
[nested] [flat]
* Possible future performance improvement: sort updates/deletes by ctid
@ 2008-01-30 03:11 Tom Lane <[email protected]>
2008-01-30 04:00 ` Re: Possible future performance improvement: sort updates/deletes by ctid Stephen Denne <[email protected]>
2008-03-23 01:23 ` Re: Possible future performance improvement: sort updates/deletes by ctid Bruce Momjian <[email protected]>
0 siblings, 2 replies; 268+ messages in thread
From: Tom Lane @ 2008-01-30 03:11 UTC (permalink / raw)
To: pgsql-hackers; +Cc: Stephen Denne <[email protected]>
We've had a couple of discussions recently revolving around the
inefficiency of using hashjoin/hashaggregation output to update a target
table, because of the resulting very random access pattern. I believe
this same mechanism is underlying the slowness of Stephen Denne's
"alternate query" described here:
http://archives.postgresql.org/pgsql-performance/2008-01/msg00227.php
I made up the attached doubtless-oversimplified test case to model what
he was seeing. It's cut down about 4x from the table size he describes,
but the UPDATE still takes forever --- I gave up waiting after 2 hours,
when it had deleted about a fifth of its hashjoin temp files, suggesting
that the total runtime would be about 10 hours.
A brute force idea for fixing this is to sort the intended update or
delete operations of an UPDATE/DELETE command according to the target
table's ctid, which is available for free anyway since the executor top
level must have it to perform the operation. I made up an even more
brute force patch (also attached) that forces that to happen for every
UPDATE or DELETE --- obviously we'd not want that for real, it's just
for crude performance testing. With that patch, I got the results
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=6075623.03..6085623.05 rows=4000008 width=618) (actual time=2078726.637..3371944.124 rows=4000000 loops=1)
Sort Key: df.ctid
Sort Method: external merge Disk: 2478992kB
-> Hash Join (cost=123330.50..1207292.72 rows=4000008 width=618) (actual time=20186.510..721120.455 rows=4000000 loops=1)
Hash Cond: (df.document_id = d.id)
-> Seq Scan on document_file df (cost=0.00..373334.08 rows=4000008 width=614) (actual time=11.775..439993.807 rows=4000000 loops=1)
-> Hash (cost=57702.00..57702.00 rows=4000200 width=8) (actual time=19575.885..19575.885 rows=4000000 loops=1)
-> Seq Scan on document d (cost=0.00..57702.00 rows=4000200 width=8) (actual time=0.039..14335.615 rows=4000000 loops=1)
Total runtime: 3684037.097 ms
or just over an hour runtime --- still not exactly speedy, but it
certainly compares favorably to the estimated 10 hours for unsorted
updates.
This is with default shared_buffers (32MB) and work_mem (1MB);
a more aggressive work_mem would have meant fewer hash batches and fewer
sort runs and hence better performance in both cases, but with the
majority of the runtime going into the sort step here, I think that the
sorted update would benefit much more.
Nowhere near a workable patch of course, but seems like food for
thought.
regards, tom lane
drop table if exists document;
drop table if exists document_file ;
create table document (document_type_id int, id int primary key);
create table document_file (document_type_id int, document_id int primary key,
filler char(600));
insert into document_file select x,x,'z' from generate_series(1,4000000) x;
insert into document select x,x from generate_series(1,4000000) x;
analyze document_file;
analyze document;
set enable_mergejoin = false;
explain analyze UPDATE ONLY document_file AS df SET document_type_id = d.document_type_id FROM document AS d WHERE d.id = document_id;
Index: src/backend/optimizer/prep/preptlist.c
===================================================================
RCS file: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v
retrieving revision 1.88
diff -c -r1.88 preptlist.c
*** src/backend/optimizer/prep/preptlist.c 1 Jan 2008 19:45:50 -0000 1.88
--- src/backend/optimizer/prep/preptlist.c 30 Jan 2008 03:06:30 -0000
***************
*** 32,37 ****
--- 32,38 ----
#include "optimizer/var.h"
#include "parser/analyze.h"
#include "parser/parsetree.h"
+ #include "parser/parse_clause.h"
#include "parser/parse_coerce.h"
***************
*** 103,108 ****
--- 104,120 ----
tlist = list_copy(tlist);
tlist = lappend(tlist, tle);
+
+ /*
+ * Force the query result to be sorted by CTID, for better update
+ * speed. (Note: we expect parse->sortClause to be NIL here,
+ * but this code will do no harm if it's not.)
+ */
+ parse->sortClause = addTargetToSortList(NULL, tle,
+ parse->sortClause, tlist,
+ SORTBY_DEFAULT,
+ SORTBY_NULLS_DEFAULT,
+ NIL, false);
}
/*
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: Possible future performance improvement: sort updates/deletes by ctid
2008-01-30 03:11 Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
@ 2008-01-30 04:00 ` Stephen Denne <[email protected]>
2008-01-30 04:39 ` Re: Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
1 sibling, 1 reply; 268+ messages in thread
From: Stephen Denne @ 2008-01-30 04:00 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: pgsql-hackers
> From: Tom Lane [mailto:[email protected]]
> doubtless-oversimplified
It looks equivalent.
> With that patch, I got the results
...
> -> Hash Join (cost=123330.50..1207292.72 rows=4000008
> width=618) (actual time=20186.510..721120.455 rows=4000000 loops=1)
The plan from here is equivalent to the query plan that I had.
In an update query, does the actual time = 721120 mean that after 12 minutes it had completed figuring out what to update, and what to?
> This is with default shared_buffers (32MB) and work_mem (1MB);
I had tried a few larger settings, and though I had fewer temp files created, they still took longer than I was willing to wait to process.
I did figure out that contention with the background writer or checkpoint processing probably wasn't a large contributor.
How hard is it to match, recognise potential benefit, and rewrite the query from
UPDATE ONLY document_file AS df SET document_type_id =
d.document_type_id FROM document AS d WHERE d.id = document_id;
to
UPDATE ONLY document_file AS df SET document_type_id =
(SELECT d.document_type_id FROM document AS d WHERE d.id = document_id);
Which is several orders of magnitude faster for me.
Stephen Denne.
Disclaimer:
At the Datamail Group we value team commitment, respect, achievement, customer focus, and courage. This email with any attachments is confidential and may be subject to legal privilege. If it is not intended for you please advise by reply immediately, destroy it and do not copy, disclose or use it in any way.
__________________________________________________________________
This email has been scanned by the DMZGlobal Business Quality
Electronic Messaging Suite.
Please see http://www.dmzglobal.com/services/bqem.htm for details.
__________________________________________________________________
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: Possible future performance improvement: sort updates/deletes by ctid
2008-01-30 03:11 Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
2008-01-30 04:00 ` Re: Possible future performance improvement: sort updates/deletes by ctid Stephen Denne <[email protected]>
@ 2008-01-30 04:39 ` Tom Lane <[email protected]>
2008-01-30 04:51 ` Re: Possible future performance improvement: sort updates/deletes by ctid Stephen Denne <[email protected]>
0 siblings, 1 reply; 268+ messages in thread
From: Tom Lane @ 2008-01-30 04:39 UTC (permalink / raw)
To: Stephen Denne <[email protected]>; +Cc: pgsql-hackers
"Stephen Denne" <[email protected]> writes:
> How hard is it to match, recognise potential benefit, and rewrite the query from
> UPDATE ONLY document_file AS df SET document_type_id =
> d.document_type_id FROM document AS d WHERE d.id = document_id;
> to
> UPDATE ONLY document_file AS df SET document_type_id =
> (SELECT d.document_type_id FROM document AS d WHERE d.id = document_id);
> Which is several orders of magnitude faster for me.
At the planner level that would be entirely the wrong way to go about
it, because that's forcing the equivalent of a nestloop join, which is
very unlikely to be faster for the numbers of rows that we're talking
about here. The reason it looks faster to you is that the benefits of
updating the document_file rows in ctid order outweigh the costs of the
dumb join strategy ... but what we want to achieve here is to have both
benefits, or at least to give the planner the opportunity to make a
cost-driven decision about what to do.
regards, tom lane
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: Possible future performance improvement: sort updates/deletes by ctid
2008-01-30 03:11 Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
2008-01-30 04:00 ` Re: Possible future performance improvement: sort updates/deletes by ctid Stephen Denne <[email protected]>
2008-01-30 04:39 ` Re: Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
@ 2008-01-30 04:51 ` Stephen Denne <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Stephen Denne @ 2008-01-30 04:51 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: pgsql-hackers
> At the planner level that would be entirely the wrong way to go about
> it, because that's forcing the equivalent of a nestloop join, which is
> very unlikely to be faster for the numbers of rows that we're talking
> about here. The reason it looks faster to you is that the benefits of
> updating the document_file rows in ctid order outweigh the
> costs of the
> dumb join strategy ... but what we want to achieve here is to
> have both
> benefits, or at least to give the planner the opportunity to make a
> cost-driven decision about what to do.
Ok.
Here are some more data points, using a smaller table, v8.2.6:
Seq Scan on document_file df (cost=0.00..208480.85 rows=25101 width=662) (actual time=0.239..773.834 rows=25149 loops=1)
SubPlan
-> Index Scan using pk_document_id on document d (cost=0.00..8.27 rows=1 width=4) (actual time=0.011..0.015 rows=1 loops=25149)
Index Cond: (id = $0)
Total runtime: 4492.363 ms
vs
Hash Join (cost=1048.85..6539.32 rows=25149 width=666) (actual time=575.079..1408.363 rows=25149 loops=1)
Hash Cond: (df.document_id = d.id)
-> Seq Scan on document_file df (cost=0.00..4987.49 rows=25149 width=662) (actual time=60.724..824.195 rows=25149 loops=1)
-> Hash (cost=734.49..734.49 rows=25149 width=8) (actual time=40.271..40.271 rows=25149 loops=1)
-> Seq Scan on document d (cost=0.00..734.49 rows=25149 width=8) (actual time=0.055..22.559 rows=25149 loops=1)
Total runtime: 34961.504 ms
These are fairly repeatable for me after doing a vacuum full analyze of the two tables.
Have I simply not tuned postgres so that it knows it has everything on a single old IDE drive, not split over a few sets of raided SSD drives, hence random_page_cost should perhaps be larger than 4.0? Would that make the second estimate larger than the first estimate?
Stephen Denne.
Disclaimer:
At the Datamail Group we value team commitment, respect, achievement, customer focus, and courage. This email with any attachments is confidential and may be subject to legal privilege. If it is not intended for you please advise by reply immediately, destroy it and do not copy, disclose or use it in any way.
__________________________________________________________________
This email has been scanned by the DMZGlobal Business Quality
Electronic Messaging Suite.
Please see http://www.dmzglobal.com/services/bqem.htm for details.
__________________________________________________________________
^ permalink raw reply [nested|flat] 268+ messages in thread
* Re: Possible future performance improvement: sort updates/deletes by ctid
2008-01-30 03:11 Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
@ 2008-03-23 01:23 ` Bruce Momjian <[email protected]>
1 sibling, 0 replies; 268+ messages in thread
From: Bruce Momjian @ 2008-03-23 01:23 UTC (permalink / raw)
To: Tom Lane <[email protected]>; +Cc: pgsql-hackers; Stephen Denne <[email protected]>
Added to TODO:
* Sort large UPDATE/DELETEs so it is done in heap order
http://archives.postgresql.org/pgsql-hackers/2008-01/msg01119.php
---------------------------------------------------------------------------
Tom Lane wrote:
> We've had a couple of discussions recently revolving around the
> inefficiency of using hashjoin/hashaggregation output to update a target
> table, because of the resulting very random access pattern. I believe
> this same mechanism is underlying the slowness of Stephen Denne's
> "alternate query" described here:
> http://archives.postgresql.org/pgsql-performance/2008-01/msg00227.php
>
> I made up the attached doubtless-oversimplified test case to model what
> he was seeing. It's cut down about 4x from the table size he describes,
> but the UPDATE still takes forever --- I gave up waiting after 2 hours,
> when it had deleted about a fifth of its hashjoin temp files, suggesting
> that the total runtime would be about 10 hours.
>
> A brute force idea for fixing this is to sort the intended update or
> delete operations of an UPDATE/DELETE command according to the target
> table's ctid, which is available for free anyway since the executor top
> level must have it to perform the operation. I made up an even more
> brute force patch (also attached) that forces that to happen for every
> UPDATE or DELETE --- obviously we'd not want that for real, it's just
> for crude performance testing. With that patch, I got the results
>
> QUERY PLAN
> -----------------------------------------------------------------------------------------------------------------------------------------------
> Sort (cost=6075623.03..6085623.05 rows=4000008 width=618) (actual time=2078726.637..3371944.124 rows=4000000 loops=1)
> Sort Key: df.ctid
> Sort Method: external merge Disk: 2478992kB
> -> Hash Join (cost=123330.50..1207292.72 rows=4000008 width=618) (actual time=20186.510..721120.455 rows=4000000 loops=1)
> Hash Cond: (df.document_id = d.id)
> -> Seq Scan on document_file df (cost=0.00..373334.08 rows=4000008 width=614) (actual time=11.775..439993.807 rows=4000000 loops=1)
> -> Hash (cost=57702.00..57702.00 rows=4000200 width=8) (actual time=19575.885..19575.885 rows=4000000 loops=1)
> -> Seq Scan on document d (cost=0.00..57702.00 rows=4000200 width=8) (actual time=0.039..14335.615 rows=4000000 loops=1)
> Total runtime: 3684037.097 ms
>
> or just over an hour runtime --- still not exactly speedy, but it
> certainly compares favorably to the estimated 10 hours for unsorted
> updates.
>
> This is with default shared_buffers (32MB) and work_mem (1MB);
> a more aggressive work_mem would have meant fewer hash batches and fewer
> sort runs and hence better performance in both cases, but with the
> majority of the runtime going into the sort step here, I think that the
> sorted update would benefit much more.
>
> Nowhere near a workable patch of course, but seems like food for
> thought.
>
> regards, tom lane
>
Content-Description: bighash.sql
> drop table if exists document;
> drop table if exists document_file ;
>
> create table document (document_type_id int, id int primary key);
> create table document_file (document_type_id int, document_id int primary key,
> filler char(600));
>
> insert into document_file select x,x,'z' from generate_series(1,4000000) x;
> insert into document select x,x from generate_series(1,4000000) x;
>
> analyze document_file;
> analyze document;
>
> set enable_mergejoin = false;
>
> explain analyze UPDATE ONLY document_file AS df SET document_type_id = d.document_type_id FROM document AS d WHERE d.id = document_id;
Content-Description: ctid-sort.patch
> Index: src/backend/optimizer/prep/preptlist.c
> ===================================================================
> RCS file: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v
> retrieving revision 1.88
> diff -c -r1.88 preptlist.c
> *** src/backend/optimizer/prep/preptlist.c 1 Jan 2008 19:45:50 -0000 1.88
> --- src/backend/optimizer/prep/preptlist.c 30 Jan 2008 03:06:30 -0000
> ***************
> *** 32,37 ****
> --- 32,38 ----
> #include "optimizer/var.h"
> #include "parser/analyze.h"
> #include "parser/parsetree.h"
> + #include "parser/parse_clause.h"
> #include "parser/parse_coerce.h"
>
>
> ***************
> *** 103,108 ****
> --- 104,120 ----
> tlist = list_copy(tlist);
>
> tlist = lappend(tlist, tle);
> +
> + /*
> + * Force the query result to be sorted by CTID, for better update
> + * speed. (Note: we expect parse->sortClause to be NIL here,
> + * but this code will do no harm if it's not.)
> + */
> + parse->sortClause = addTargetToSortList(NULL, tle,
> + parse->sortClause, tlist,
> + SORTBY_DEFAULT,
> + SORTBY_NULLS_DEFAULT,
> + NIL, false);
> }
>
> /*
>
> ---------------------------(end of broadcast)---------------------------
> TIP 1: if posting/reading through Usenet, please send an appropriate
> subscribe-nomail command to [email protected] so that your
> message can get through to the mailing list cleanly
--
Bruce Momjian <[email protected]> http://momjian.us
EnterpriseDB http://postgres.enterprisedb.com
+ If your life is a hard drive, Christ can be your backup. +
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
* [PATCH v4] Restructure CreateStatsStmt parse-analysis processing
@ 2025-10-17 11:55 Álvaro Herrera <[email protected]>
0 siblings, 0 replies; 268+ messages in thread
From: Álvaro Herrera @ 2025-10-17 11:55 UTC (permalink / raw)
Previously, a lot of code would be run in the ProcessUtility pipeline,
where it is not welcome (that's supposed to be just a giant dispatcher
switch). Move the parse analysis code to CreateStatistics() instead,
which then means we don't need to open the relation twice; it also
allows us to give a better error message when something other than a
relation name is given in the FROM clause. It also means we no longer
need to do the transform step in ATPostAlterTypeParse(), which saves a
bit of code.
Author: Jian He <[email protected]>
Discussion: https://postgr.es/m/CACJufxFcvo=fM6J9RC88n1U=eS9+5QdftwUNQMu5h7OgjmqDwQ@mail.gmail.com
---
src/backend/commands/statscmds.c | 13 +++++++++++--
src/backend/commands/tablecmds.c | 16 +---------------
src/backend/parser/parse_utilcmd.c | 13 +++----------
src/backend/tcop/utility.c | 29 +----------------------------
src/include/commands/defrem.h | 2 +-
src/include/parser/parse_utilcmd.h | 2 +-
6 files changed, 18 insertions(+), 57 deletions(-)
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 27bf67e7c4b..632228c13ed 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -30,6 +30,7 @@
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
+#include "parser/parse_utilcmd.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/builtins.h"
@@ -58,9 +59,14 @@ compare_int16(const void *a, const void *b)
/*
* CREATE STATISTICS
+ *
+ * queryString is the source text of the CREATE STATISTICS command, if any.
+ *
+ * Normally stmt has not already undergone transformation, but in some cases
+ * it might have, such as CREATE TABLE (LIKE).
*/
ObjectAddress
-CreateStatistics(CreateStatsStmt *stmt)
+CreateStatistics(CreateStatsStmt *stmt, const char *queryString)
{
int16 attnums[STATS_MAX_DIMENSIONS];
int nattnums = 0;
@@ -113,7 +119,7 @@ CreateStatistics(CreateStatsStmt *stmt)
if (!IsA(rln, RangeVar))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("only a single relation is allowed in CREATE STATISTICS")));
+ errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
/*
* CREATE STATISTICS will influence future execution plans but does
@@ -151,6 +157,9 @@ CreateStatistics(CreateStatsStmt *stmt)
Assert(rel);
relid = RelationGetRelid(rel);
+ /* Run parse analysis */
+ stmt = transformStatsStmt(rel, stmt, queryString);
+
/*
* If the node has a name, split it up and determine creation namespace.
* If not, put the object in the same namespace as the relation, and cons
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd8b51312c..a1732b71e55 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -9650,16 +9650,7 @@ static ObjectAddress
ATExecAddStatistics(AlteredTableInfo *tab, Relation rel,
CreateStatsStmt *stmt, bool is_rebuild, LOCKMODE lockmode)
{
- ObjectAddress address;
-
- Assert(IsA(stmt, CreateStatsStmt));
-
- /* The CreateStatsStmt has already been through transformStatsStmt */
- Assert(stmt->transformed);
-
- address = CreateStatistics(stmt);
-
- return address;
+ return CreateStatistics(stmt, NULL);
}
/*
@@ -15632,11 +15623,6 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
querytree_list = lappend(querytree_list, stmt);
querytree_list = list_concat(querytree_list, afterStmts);
}
- else if (IsA(stmt, CreateStatsStmt))
- querytree_list = lappend(querytree_list,
- transformStatsStmt(oldRelId,
- (CreateStatsStmt *) stmt,
- cmd));
else
querytree_list = lappend(querytree_list, stmt);
}
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index e96b38a59d5..89c8315117b 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -3137,16 +3137,14 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
* transformStatsStmt - parse analysis for CREATE STATISTICS
*
* To avoid race conditions, it's important that this function relies only on
- * the passed-in relid (and not on stmt->relation) to determine the target
- * relation.
+ * the passed-in rel (and not on stmt->relation) as the target relation.
*/
CreateStatsStmt *
-transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
+transformStatsStmt(Relation rel, CreateStatsStmt *stmt, const char *queryString)
{
ParseState *pstate;
ParseNamespaceItem *nsitem;
ListCell *l;
- Relation rel;
/* Nothing to do if statement already transformed. */
if (stmt->transformed)
@@ -3158,10 +3156,8 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
/*
* Put the parent table into the rtable so that the expressions can refer
- * to its fields without qualification. Caller is responsible for locking
- * relation, but we still need to open it.
+ * to its fields without qualification.
*/
- rel = relation_open(relid, NoLock);
nsitem = addRangeTableEntryForRelation(pstate, rel,
AccessShareLock,
NULL, false, true);
@@ -3196,9 +3192,6 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString)
free_parsestate(pstate);
- /* Close relation */
- table_close(rel, NoLock);
-
/* Mark statement as successfully transformed */
stmt->transformed = true;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 918db53dd5e..027356f6e77 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1866,34 +1866,7 @@ ProcessUtilitySlow(ParseState *pstate,
break;
case T_CreateStatsStmt:
- {
- Oid relid;
- CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree;
- RangeVar *rel = (RangeVar *) linitial(stmt->relations);
-
- if (!IsA(rel, RangeVar))
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("CREATE STATISTICS only supports relation names in the FROM clause")));
-
- /*
- * CREATE STATISTICS will influence future execution plans
- * but does not interfere with currently executing plans.
- * So it should be enough to take ShareUpdateExclusiveLock
- * on relation, conflicting with ANALYZE and other DDL
- * that sets statistical information, but not with normal
- * queries.
- *
- * XXX RangeVarCallbackOwnsRelation not needed here, to
- * keep the same behavior as before.
- */
- relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false);
-
- /* Run parse analysis ... */
- stmt = transformStatsStmt(relid, stmt, queryString);
-
- address = CreateStatistics(stmt);
- }
+ address = CreateStatistics((CreateStatsStmt *) parsetree, queryString);
break;
case T_AlterStatsStmt:
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index dd22b5efdfd..684f62109fd 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
/* commands/statscmds.c */
-extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, const char *queryString);
extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
extern void RemoveStatisticsById(Oid statsOid);
extern void RemoveStatisticsDataById(Oid statsOid, bool inh);
diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h
index 4965fac4495..67255799f0a 100644
--- a/src/include/parser/parse_utilcmd.h
+++ b/src/include/parser/parse_utilcmd.h
@@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
List **afterStmts);
extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt,
const char *queryString);
-extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt,
+extern CreateStatsStmt *transformStatsStmt(Relation rel, CreateStatsStmt *stmt,
const char *queryString);
extern void transformRuleStmt(RuleStmt *stmt, const char *queryString,
List **actions, Node **whereClause);
--
2.47.3
--xzcma6eafn4gmiua--
^ permalink raw reply [nested|flat] 268+ messages in thread
end of thread, other threads:[~2025-10-17 11:55 UTC | newest]
Thread overview: 268+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2008-01-30 03:11 Possible future performance improvement: sort updates/deletes by ctid Tom Lane <[email protected]>
2008-01-30 04:00 ` Stephen Denne <[email protected]>
2008-01-30 04:39 ` Tom Lane <[email protected]>
2008-01-30 04:51 ` Stephen Denne <[email protected]>
2008-03-23 01:23 ` Bruce Momjian <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure CreateStatsStmt parse-analysis processing Álvaro Herrera <[email protected]>
2025-10-17 11:55 [PATCH v4] Restructure 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