agora inbox for [email protected]help / color / mirror / Atom feed
make LockRelation use top transaction ID 270+ messages / 3 participants [nested] [flat]
* make LockRelation use top transaction ID @ 2004-07-23 03:05 Alvaro Herrera <[email protected]> 0 siblings, 1 reply; 270+ messages in thread From: Alvaro Herrera @ 2004-07-23 03:05 UTC (permalink / raw) To: pgsql-hackers Hackers, I just figured that if we let LockRelation use GetCurrentTransactionId() then the wrong thing happens if we let large objects survive subtransaction commit/abort. The problem is that when closing a large object at main transaction commit, which was opened inside a subtransaction, the code tries to UnlockRelation(pg_largeobject), and use the main transaction Xid (instead of the subtransaction Xid that it used to LockRelation()). So I have changed it to use GetTopTransactionId() instead. Is that OK with everybody? -- Alvaro Herrera (<alvherre[a]dcc.uchile.cl>) Maybe there's lots of data loss but the records of data loss are also lost. (Lincoln Yeoh) ^ permalink raw reply [nested|flat] 270+ messages in thread
* Re: make LockRelation use top transaction ID @ 2004-07-23 13:49 Tom Lane <[email protected]> parent: Alvaro Herrera <[email protected]> 0 siblings, 1 reply; 270+ messages in thread From: Tom Lane @ 2004-07-23 13:49 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers Alvaro Herrera <[email protected]> writes: > I just figured that if we let LockRelation use GetCurrentTransactionId() > then the wrong thing happens if we let large objects survive > subtransaction commit/abort. > So I have changed it to use GetTopTransactionId() instead. Is that OK > with everybody? No, at least not if you made that a global change. Doing it that way will mean that a failed subtransaction will not release its locks, no? regards, tom lane ^ permalink raw reply [nested|flat] 270+ messages in thread
* Re: make LockRelation use top transaction ID @ 2004-07-24 15:03 Alvaro Herrera <[email protected]> parent: Tom Lane <[email protected]> 0 siblings, 1 reply; 270+ messages in thread From: Alvaro Herrera @ 2004-07-24 15:03 UTC (permalink / raw) To: Tom Lane <[email protected]>; +Cc: pgsql-hackers On Fri, Jul 23, 2004 at 09:49:05AM -0400, Tom Lane wrote: > Alvaro Herrera <[email protected]> writes: > > I just figured that if we let LockRelation use GetCurrentTransactionId() > > then the wrong thing happens if we let large objects survive > > subtransaction commit/abort. > > > So I have changed it to use GetTopTransactionId() instead. Is that OK > > with everybody? > > No, at least not if you made that a global change. Doing it that way > will mean that a failed subtransaction will not release its locks, no? Hmm ... won't they be released when the ResourceOwner is released? -- Alvaro Herrera (<alvherre[a]dcc.uchile.cl>) "Ni aun el genio muy grande llegaría muy lejos si tuviera que sacarlo todo de su propio interior" (Goethe) ^ permalink raw reply [nested|flat] 270+ messages in thread
* Re: make LockRelation use top transaction ID @ 2004-07-24 20:08 Tom Lane <[email protected]> parent: Alvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Tom Lane @ 2004-07-24 20:08 UTC (permalink / raw) To: Alvaro Herrera <[email protected]>; +Cc: pgsql-hackers Alvaro Herrera <[email protected]> writes: > On Fri, Jul 23, 2004 at 09:49:05AM -0400, Tom Lane wrote: >> No, at least not if you made that a global change. Doing it that way >> will mean that a failed subtransaction will not release its locks, no? > Hmm ... won't they be released when the ResourceOwner is released? Er ... duh. Still stuck in pre-ResourceOwner ways of thinking ;-) Now that I'm more awake, I recall that I actually considered changing the lock code to take all locks in the name of the TopTransaction as part of the ResourceOwner patch. But I decided to leave well enough alone because I hadn't time to think about all the implications. Yeah, if you don't see any problem, go for it. regards, tom lane ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
* [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt @ 2025-08-28 17:49 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 270+ messages in thread From: Álvaro Herrera @ 2025-08-28 17:49 UTC (permalink / raw) --- src/backend/commands/statscmds.c | 26 +++++----------- src/backend/commands/tablecmds.c | 4 +-- src/backend/parser/parse_utilcmd.c | 13 +++++--- src/backend/tcop/utility.c | 48 +++++++++++++++++------------- src/include/commands/defrem.h | 2 +- src/include/parser/parse_utilcmd.h | 2 +- 6 files changed, 49 insertions(+), 46 deletions(-) diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c index e24d540cd45..07b2b5bfef3 100644 --- a/src/backend/commands/statscmds.c +++ b/src/backend/commands/statscmds.c @@ -59,7 +59,7 @@ compare_int16(const void *a, const void *b) * CREATE STATISTICS */ ObjectAddress -CreateStatistics(CreateStatsStmt *stmt) +CreateStatistics(CreateStatsStmt *stmt, List *relids) { int16 attnums[STATS_MAX_DIMENSIONS]; int nattnums = 0; @@ -92,28 +92,18 @@ CreateStatistics(CreateStatsStmt *stmt) ListCell *cell; ListCell *cell2; - Assert(IsA(stmt, CreateStatsStmt)); - /* * Examine the FROM clause. Currently, we only allow it to be a single * simple table, but later we'll probably allow multiple tables and JOIN - * syntax. The grammar is already prepared for that, so we have to check - * here that what we got is what we can support. + * syntax. Parse analysis checked the list length already, so this is + * just defense-in-depth. */ - if (list_length(stmt->relations) != 1) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + Assert(list_length(stmt->relations) == list_length(relids)); + if (list_length(relids) != 1) + elog(ERROR, "only a single relation is allowed in CREATE STATISTICS"); - foreach(cell, stmt->relations) + foreach_oid(relid, relids) { - Node *rln = (Node *) lfirst(cell); - - if (!IsA(rln, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); - /* * CREATE STATISTICS will influence future execution plans but does * not interfere with currently executing plans. So it should be @@ -121,7 +111,7 @@ CreateStatistics(CreateStatsStmt *stmt) * conflicting with ANALYZE and other DDL that sets statistical * information, but not with normal queries. */ - rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock); + rel = relation_open(relid, ShareUpdateExclusiveLock); /* Restrict to allowed relation types */ if (rel->rd_rel->relkind != RELKIND_RELATION && diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 082a3575d62..1779bae80cb 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -9654,7 +9654,7 @@ ATExecAddStatistics(AlteredTableInfo *tab, Relation rel, /* The CreateStatsStmt has already been through transformStatsStmt */ Assert(stmt->transformed); - address = CreateStatistics(stmt); + address = CreateStatistics(stmt, list_make1_oid(RelationGetRelid(rel))); return address; } @@ -15631,7 +15631,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd, } else if (IsA(stmt, CreateStatsStmt)) querytree_list = lappend(querytree_list, - transformStatsStmt(oldRelId, + transformStatsStmt(list_make1_oid(oldRelId), (CreateStatsStmt *) stmt, cmd)); else diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index afcf54169c3..587e2ef439c 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -3138,11 +3138,11 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString) * transformStatsStmt - parse analysis for CREATE STATISTICS * * To avoid race conditions, it's important that this function relies only on - * the passed-in relid (and not on stmt->relation) to determine the target - * relation. + * the passed-in relids list (and not on stmt->relations) to determine the + * target relation. */ CreateStatsStmt * -transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) +transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString) { ParseState *pstate; ParseNamespaceItem *nsitem; @@ -3153,6 +3153,11 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) if (stmt->transformed) return stmt; + if (list_length(relids) != 1) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("only a single relation is allowed in CREATE STATISTICS")); + /* Set up pstate */ pstate = make_parsestate(NULL); pstate->p_sourcetext = queryString; @@ -3162,7 +3167,7 @@ transformStatsStmt(Oid relid, CreateStatsStmt *stmt, const char *queryString) * to its fields without qualification. Caller is responsible for locking * relation, but we still need to open it. */ - rel = relation_open(relid, NoLock); + rel = relation_open(linitial_oid(relids), NoLock); nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock, NULL, false, true); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4f4191b0ea6..e09da051310 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1867,32 +1867,40 @@ ProcessUtilitySlow(ParseState *pstate, case T_CreateStatsStmt: { - Oid relid; CreateStatsStmt *stmt = (CreateStatsStmt *) parsetree; - RangeVar *rel = (RangeVar *) linitial(stmt->relations); + List *relids = NIL; + ListCell *cell; - if (!IsA(rel, RangeVar)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("only a single relation is allowed in CREATE STATISTICS"))); + foreach(cell, stmt->relations) + { + Oid relid; - /* - * CREATE STATISTICS will influence future execution plans - * but does not interfere with currently executing plans. - * So it should be enough to take ShareUpdateExclusiveLock - * on relation, conflicting with ANALYZE and other DDL - * that sets statistical information, but not with normal - * queries. - * - * XXX RangeVarCallbackOwnsRelation not needed here, to - * keep the same behavior as before. - */ - relid = RangeVarGetRelid(rel, ShareUpdateExclusiveLock, false); + if (!IsA(lfirst(cell), RangeVar)) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create statistics on specified relation"), + errdetail("CREATE STATISTICS only supports tables, materialized views, foreign tables, and partitioned tables.")); + /* + * CREATE STATISTICS will influence future execution + * plans but does not interfere with currently + * executing plans. So it should be enough to take + * ShareUpdateExclusiveLock on relation, conflicting + * with ANALYZE and other DDL that sets statistical + * information, but not with normal queries. + * + * XXX RangeVarCallbackOwnsRelation not needed here, + * to keep the same behavior as before. + */ + relid = RangeVarGetRelid(castNode(RangeVar, lfirst(cell)), + ShareUpdateExclusiveLock, false); + relids = lappend_oid(relids, relid); + } /* Run parse analysis ... */ - stmt = transformStatsStmt(relid, stmt, queryString); + stmt = transformStatsStmt(relids, stmt, queryString); - address = CreateStatistics(stmt); + /* ... and execute the command */ + address = CreateStatistics(stmt, relids); } break; diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index dd22b5efdfd..7b8cb40cd83 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -85,7 +85,7 @@ extern void RemoveOperatorById(Oid operOid); extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt); /* commands/statscmds.c */ -extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt); +extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt, List *relids); extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt); extern void RemoveStatisticsById(Oid statsOid); extern void RemoveStatisticsDataById(Oid statsOid, bool inh); diff --git a/src/include/parser/parse_utilcmd.h b/src/include/parser/parse_utilcmd.h index 9f2b58de797..28165eb1198 100644 --- a/src/include/parser/parse_utilcmd.h +++ b/src/include/parser/parse_utilcmd.h @@ -26,7 +26,7 @@ extern AlterTableStmt *transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, List **afterStmts); extern IndexStmt *transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString); -extern CreateStatsStmt *transformStatsStmt(Oid relid, CreateStatsStmt *stmt, +extern CreateStatsStmt *transformStatsStmt(List *relids, CreateStatsStmt *stmt, const char *queryString); extern void transformRuleStmt(RuleStmt *stmt, const char *queryString, List **actions, Node **whereClause); -- 2.39.5 --pbl4mx6qlbtkw6pf Content-Type: text/x-diff; charset=utf-8 Content-Disposition: attachment; filename="0001-CREATE-STATISTICS-Fix-error-message.patch" ^ permalink raw reply [nested|flat] 270+ messages in thread
end of thread, other threads:[~2025-08-28 17:49 UTC | newest] Thread overview: 270+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2004-07-23 03:05 make LockRelation use top transaction ID Alvaro Herrera <[email protected]> 2004-07-23 13:49 ` Tom Lane <[email protected]> 2004-07-24 15:03 ` Alvaro Herrera <[email protected]> 2004-07-24 20:08 ` Tom Lane <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Álvaro Herrera <[email protected]> 2025-08-28 17:49 [PATCH] rewrite ProcessUtilitySlow code for CreateStatsStmt Á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